import { BridgeConfigType } from "../config.type";
import { exrpd } from "../util/exrpd";
import keccak256 from "keccak256";
import { BridgeDoorContract, Contract, SafeCoreContracts, XrpSafeContract } from "./contract";
import { CreateXrpBridge } from "./CreateXrpBridge";
import fs from "fs";
import path from "path";
import ejs from "ejs";
import { BridgeDoorContractAddress } from "./contract/BridgeDoorContract";
import { Log, LogStatus, LogType } from "../../util/Logger";

export class BridgeService {
    xrpBridgeAddress?: string;

    constructor(private config: BridgeConfigType, private exportPath: string) {
        if (this.config.xrplAddress) this.xrpBridgeAddress = this.config.xrplAddress;
    }

    get xrpSafeContract() {
        return XrpSafeContract(
            this.config.witnesses.map((w) => w.evmAddress),
            this.config.quorum,
        );
    }

    private getNextAvailableAccountNumber(genesis: any): string {
        for (let i = 0; i < genesis["app_state"]["auth"]["accounts"].length; i++) {
            let found = false;
            for (const account of genesis["app_state"]["auth"]["accounts"]) {
                if (account["base_account"]["account_number"] === i.toString()) {
                    found = true;
                    break;
                }
            }
            if (!found) return i.toString();
        }
        return genesis["app_state"]["auth"]["accounts"].length.toString();
    }

    private addGenesisContract(contract: Contract, genesis: any): any {
        const cosmosAddress = JSON.parse(exrpd(`keys parse ${contract.address.replace("0x", "")} --output json`)).formats[0];
        const codeHash = keccak256(Buffer.from(contract.bytecode, "hex")).toString("hex");

        let accountFound = false;
        for (let i = 0; i < genesis["app_state"]["auth"]["accounts"].length; i++) {
            const account = genesis["app_state"]["auth"]["accounts"][i];
            if (account.base_account.address === cosmosAddress) {
                genesis["app_state"]["auth"]["accounts"][i].code_hash = "0x" + codeHash;
                accountFound = true;
                break;
            }
        }
        if (!accountFound) {
            genesis["app_state"]["auth"]["accounts"].push({
                "@type": "/ethermint.types.v1.EthAccount",
                base_account: {
                    address: cosmosAddress,
                    pub_key: null,
                    account_number: this.getNextAvailableAccountNumber(genesis),
                    sequence: "1",
                },
                code_hash: "0x" + codeHash,
            });
        }

        genesis["app_state"]["evm"]["accounts"].push({
            address: contract.address,
            code: contract.bytecode,
            storage: contract.memory,
        });

        return genesis;
    }

    addBridgeContracts(genesis: any): any {
        if (!this.xrpBridgeAddress) {
            throw new Error("XRP bridge not configured yet");
        }
        for (const contract of SafeCoreContracts) {
            genesis = this.addGenesisContract(contract, genesis);
        }

        genesis = this.addGenesisContract(this.xrpSafeContract, genesis);

        genesis = this.addGenesisContract(
            BridgeDoorContract(this.xrpBridgeAddress, this.config.minCreateAmount, this.config.minRewardAmount),
            genesis,
        );

        return genesis;
    }

    async createXrpBridge() {
        Log(LogType.Bridge, LogStatus.ToDo, "Creating XRP bridge...");
        this.xrpBridgeAddress = await CreateXrpBridge(this.config);
        Log(LogType.Bridge, LogStatus.Done, `XRP bridge created at address: ${this.xrpBridgeAddress}`);
    }

    async configure(evmRpc: string) {
        Log(LogType.Bridge, LogStatus.ToDo, "Configuring bridge witness servers...");
        if (!this.xrpBridgeAddress) {
            throw new Error("XRP bridge not configured yet");
        }
        const configTemplate = fs.readFileSync(path.join(__dirname, "../template/witness-config.yml.ejs")).toString();
        const envTemplate = fs.readFileSync(path.join(__dirname, "../template/witness.env.ejs")).toString();
        for (let i = 0; i < this.config.witnesses.length; i++) {
            const witness = this.config.witnesses[i];
            const configResult = ejs.render(configTemplate, {
                xrpNode: this.config.xrplRpc,
                xrpBridgeAddress: this.xrpBridgeAddress,
                evmNode: evmRpc,
                evmBridgeAddress: BridgeDoorContractAddress,
            });
            const envResult = ejs.render(envTemplate, {
                awsRegion: witness.region,
                awsAccessKey: witness.accessKey,
                awsSecretAccessKey: witness.secretAccessKey,
                awsKeyId: witness.keyId,
            });
            fs.writeFileSync(path.join(this.exportPath, `witness-${i}-config.yml`), configResult);
            fs.writeFileSync(path.join(this.exportPath, `witness-${i}.env`), envResult);
            Log(LogType.Bridge, LogStatus.Working, `Bridge witness server ${i} configured`);
        }
        Log(LogType.Bridge, LogStatus.Done, `All bridge witness servers configured`);
    }
}
